﻿using System;
using System.Collections.Generic;
using System.Device.Location;
using System.Text.RegularExpressions;

namespace AVICFEEDSApp
{
	/// <summary>
	/// This class is used to encode and decode the GPS coordinates stored in a POI for Pioneer navigation systems.  The encoding works using a key to replace characters with certain values.
	/// </summary>
	internal static class AVICDecoderRing
	{
		/// <summary>
		/// This encoding key indicates which characters to replace
		/// </summary>
		private const string EncodingKey = "09152631435867708294.I+P-O";
		/// <summary>
		/// A Regular Expression used to search for existing encoding
		/// </summary>
		private const string EncodingRegEx = @"@\[([0-9OIP]*)\]\[([0-9OIP]*)\]@";
		/// <summary>
		/// A format string used to build an encoding coordinate
		/// </summary>
		private const string EncodingFormatString = "@[{0}][{1}]@";

		/// <summary>
		/// Stores key/values for characters to replace when encoding
		/// </summary>
		private static Dictionary<char, char> _forwardLookupDictionary = new Dictionary<char, char>();

		/// <summary>
		/// Stores key/values for characters to replace when decoding
		/// </summary>
		private static Dictionary<char, char> _reverseLookupDictionary = new Dictionary<char, char>();


		static AVICDecoderRing()
		{
			// Fill the encoding and decoding dictionaries
			for (int i = 0; i < EncodingKey.Length; i += 2)
			{
				AVICDecoderRing._forwardLookupDictionary.Add(EncodingKey[i], EncodingKey[i + 1]);
				AVICDecoderRing._reverseLookupDictionary.Add(EncodingKey[i + 1], EncodingKey[i]);
			}
		}

		/// <summary>
		/// Gets a dictionary containing characters and their forward lookup counterparts.
		/// </summary>
		private static Dictionary<char, char> ForwardLookupDictionary
		{
			get
			{
				return AVICDecoderRing._forwardLookupDictionary;
			}
		}

		/// <summary>
		/// Gets a dictionary containing characters and their reverse lookup counterparts.
		/// </summary>
		private static Dictionary<char, char> ReverseLookupDictionary
		{
			get
			{
				return AVICDecoderRing._forwardLookupDictionary;
			}
		}


		/// <summary>
		/// Encodes a double value to a string in the special Pioneer encoding.
		/// </summary>
		/// <param name="value">A double value that represents a latitude or longitude</param>
		/// <returns>The encoded version of the double value passed.</returns>
		private static string EncodeInternal(double value)
		{
			// Convert the input to a double
			string original = value.ToString("0.000000");
			string ret = "";

			// Build a string containing replaced characters
			for (int i = 0; i < original.Length; i++)
			{
				ret += AVICDecoderRing.ForwardLookupDictionary[original[i]];
			}

			return ret;
		}

		/// <summary>
		/// Decodes a string from an encoded Pioneer value to a double representing a latitude or longitude
		/// </summary>
		/// <param name="value">An array of chars that contains an encoded latitude or longitude.</param>
		/// <returns>The decoded version of the string that was passed.</returns>
		private static double DecodeInternal(string value)
		{
			// Build a string with the decoded values
			string ret = "";

			for (int i = 0; i < value.Length; i++)
			{
				ret += AVICDecoderRing.ReverseLookupDictionary[value[i]];
			}

			// Attempt to parse the decoded string as a double
			double parsedValue = 0;
			if (double.TryParse(ret, out parsedValue) == true)
			{
				return parsedValue;
			}
			return double.MaxValue;
		}

		/// <summary>
		/// Encodes a GeoCoordinate into the special Pioneer value.
		/// </summary>
		/// <param name="geocoordinate">A GeoCoordinate con</param>
		/// <returns></returns>
		public static string Encode(GeoCoordinate geocoordinate)
		{
			return String.Format(EncodingFormatString, AVICDecoderRing.EncodeInternal(geocoordinate.Longitude), AVICDecoderRing.EncodeInternal(geocoordinate.Latitude));
		}

		/// <summary>
		/// Encodes the specified longitude/latitude into the special Pioneer value.
		/// </summary>
		/// <param name="longitude">A double representing a longitude.</param>
		/// <param name="latitude">A double representing a latitude.</param>
		/// <returns>The encoded version of the longitude/latitude.</returns>
		public static string Encode(double longitude, double latitude)
		{
			return String.Format(EncodingFormatString, AVICDecoderRing.EncodeInternal(longitude), AVICDecoderRing.EncodeInternal(latitude));
		}

		/// <summary>
		/// Decodes a string encoded with the special Pioneer encoding.
		/// </summary>
		/// <param name="value">An encoded string.</param>
		/// <returns>The GeoCoordinate encoded in the specified string.</returns>
		public static GeoCoordinate Decode(string value)
		{
			Regex regEx = new Regex(EncodingRegEx, RegexOptions.IgnorePatternWhitespace | RegexOptions.IgnoreCase);

			if (regEx.IsMatch(value) == true)
			{
				// Find the pattern in the input string
				Match match = regEx.Match(value);

				if (match.Groups.Count == 3)
				{
					// Decode the longitude and latitude
					double longitude = AVICDecoderRing.DecodeInternal(match.Groups[1].Value);
					double latitude = AVICDecoderRing.DecodeInternal(match.Groups[2].Value);

					// Return the longitude and latitude as a GeoCoordinate
					if (longitude != double.MaxValue && latitude != double.MaxValue)
					{
						return new GeoCoordinate(latitude, longitude);
					}
				}
			}

			return GeoCoordinate.Unknown;
		}
	}
}
